package com.gmadmin.sysadmin.security.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.gmadmin.commoncore.api.R;
import com.gmadmin.sysadmin.security.enums.RequestMethodEnum;
import com.gmadmin.sysadmin.security.filter.OpenPolicyAgentAuthorizationManager;
import com.gmadmin.sysadmin.security.filter.TokenFilter;
import com.gmadmin.sysadmin.security.filter.TokenProvider;
import com.gmadmin.sysadmin.security.service.OnlineUserService;
import com.gmadmin.sysadmin.security.utils.AnonTagUtils;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.config.core.GrantedAuthorityDefaults;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.encrypt.KeyStoreKeyFactory;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

import java.io.IOException;
import java.security.KeyPair;
import java.util.Map;
import java.util.Set;

@Configuration
@EnableMethodSecurity(securedEnabled = true)
public class SpringSecurityConfig {

    @Autowired
    private TokenProvider tokenProvider;
    @Autowired
    private ApplicationContext applicationContext;
    @Autowired
    private SecurityProperties properties;
    @Autowired
    private OnlineUserService onlineUserService;

    @Bean
    public GrantedAuthorityDefaults grantedAuthorityDefaults() {
        // 去除 ROLE_ 前缀
        return new GrantedAuthorityDefaults("");
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        // 密码加密方式
        return new BCryptPasswordEncoder();
    }

    @Bean
    protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        // 获取匿名标记
        Map<String, Set<String>> anonymousUrls = AnonTagUtils.getAnonymousUrl(applicationContext);
        return httpSecurity
                // 禁用 CSRF
                .csrf(AbstractHttpConfigurer::disable)
                //.addFilter(corsFilter())
                // 授权异常
                .exceptionHandling(handling ->
                        handling.authenticationEntryPoint(new AuthenticationEntryPoint() {
                                    @Override
                                    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
                                        // 当用户尝试访问安全的REST资源而不提供任何凭据时，将调用此方法发送401 响应
                                        int code = HttpStatus.UNAUTHORIZED.value();
                                        response.setStatus(code);
                                        response.setContentType("application/json;charset=UTF-8");
                                        ObjectMapper objectMapper = new ObjectMapper();
                                        String jsonResponse = objectMapper.writeValueAsString(R.unauthorized("登录状态已过期，请重新登录"));
                                        response.getWriter().write(jsonResponse);
                                    }
                                })
                                .accessDeniedHandler(new AccessDeniedHandler() {
                                    @Override
                                    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
                                        //当用户在没有授权的情况下访问受保护的REST资源时，将调用此方法发送403 Forbidden响应
                                        response.setStatus(HttpStatus.FORBIDDEN.value());
                                        response.setContentType("application/json;charset=UTF-8");
                                        ObjectMapper objectMapper = new ObjectMapper();
                                        String jsonResponse = objectMapper.writeValueAsString(R.forbidden("禁止访问，您没有权限访问此资源"));
                                        response.getWriter().write(jsonResponse);
                                    }
                                }))
                // 防止iframe 造成跨域
                .headers(e -> {
                    e.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable);
                    e.disable();
                })
                // 不创建会话
                .sessionManagement(e -> {
                    e.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
                })
                .authorizeHttpRequests(e -> {
                    e.requestMatchers(HttpMethod.GET,
                            "/*.html",
                            "/*/*.html",
                            "/*/*.css",
                            "/*/*.js",
                            "/webSocket/**").permitAll();
                    e.requestMatchers(
                            "/swagger-ui.html", //swagger 文档
                            "/webjars/css/*",   //swagger 文档
                            "/webjars/js/*",    //swagger 文档
                            "/swagger-resources/*",  //swagger 文档
                            "/webjars/*",      //swagger 文档
                            "/*/api-docs",      //swagger 文档
                            "/*/api-docs/*",      //swagger 文档
                            "/avatar/*",       //文件
                            "/file/*",         //文件
                            "/druid/*"  //阿里巴巴 druid
                            ).permitAll();
                    // 放行OPTIONS请求
                    e.requestMatchers(HttpMethod.POST, "/*").permitAll();
                    // 自定义匿名访问所有url放行：允许匿名和带Token访问，细腻化到每个 Request 类型
                    // GET
                    e.requestMatchers(HttpMethod.GET, anonymousUrls.get(RequestMethodEnum.GET.getType()).toArray(new String[0])).permitAll();
                    // POST
                    e.requestMatchers(HttpMethod.POST, anonymousUrls.get(RequestMethodEnum.POST.getType()).toArray(new String[0])).permitAll();
                    // PUT
                    e.requestMatchers(HttpMethod.PUT, anonymousUrls.get(RequestMethodEnum.PUT.getType()).toArray(new String[0])).permitAll();
                    // PATCH
                    e.requestMatchers(HttpMethod.PATCH, anonymousUrls.get(RequestMethodEnum.PATCH.getType()).toArray(new String[0])).permitAll();
                    // DELETE
                    e.requestMatchers(HttpMethod.DELETE, anonymousUrls.get(RequestMethodEnum.DELETE.getType()).toArray(new String[0])).permitAll();
                    // 所有类型的接口都放行
                    e.requestMatchers(anonymousUrls.get(RequestMethodEnum.ALL.getType()).toArray(new String[0])).permitAll();
                    // 其它所有请求都需要验证
                    e.anyRequest().access(new OpenPolicyAgentAuthorizationManager());
                })
                .addFilterBefore(new TokenFilter(tokenProvider, properties, onlineUserService), UsernamePasswordAuthenticationFilter.class)
                .build();
    }

    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOriginPattern("*");
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }


}
